using System.Runtime.CompilerServices;
using Pinwheel.Toolkits;

namespace Pinwheel.LSerialize
{
    public static unsafe partial class LSerializer
    {
        public static byte[] SafeSerialize<T>(ref T value, bool pack = true)
        {
            var context = new LSerializeContext(0);
            var writer = new LStreamWriter(LStreamBuffer.DefaultSize);
            byte[] result;
            try
            {
                writer.Seek(0, SeekOrigin.Begin);
                Serialize(&context, &writer, ref value);
                if (pack)
                {
                    LStreamBuffer* packed = LStreamBuffer.Create();
                    Pack(writer.Buffer, packed);
                    result = packed->GetBuffer();
                    packed->Dispose();
                    Native.Current.Free(packed);
                }
                else
                {
                    result = writer.GetBuffer();
                }

                LSerializeExtension.Cache.RemoveAllReference();
            }
            finally
            {
                writer.Dispose();
                context.Dispose();
            }

            return result;
        }

        public static byte[] Serialize<T>(ref T value, bool pack = true)
        {
            if (null == LSerializeContext.Default) LSerializeContext.InitDefault();

            var context = LSerializeContext.Default;
            LStreamWriter* writer = LStreamWriter.Default;

            context->Clear();
            writer->Reset();

            byte[] result;
            Profiler.Start("LSerialize.Core");
            Serialize(context, writer, ref value);
            Profiler.End();
            
            // Log.Info($"Serialize pack start: {pack}, {writer->Buffer->Size}, {BitConverter.ToString(writer->GetBuffer())}");

            if (pack)
            {
                Profiler.Start("LSerialize.Packed");
                LStreamBuffer* packed = LStreamBuffer.Create();
                Pack(writer->Buffer, packed);
                Profiler.Start("LSerialize.GetBuffer");
                result = packed->GetBuffer();
                Profiler.End();
                packed->Dispose();
                Native.Current.Free(packed);
                Profiler.End();
            }
            else
            {
                Profiler.Start("LSerialize.GetBuffer");
                result = writer->GetBuffer();
                Profiler.End();
            }
            
            // Log.Info($"Serialize pack complete: {pack}, {result.Length}, {BitConverter.ToString(result)}");

            Profiler.Start("LSerialize.RemoveCache");
            LSerializeExtension.Cache.RemoveAllReference();
            Profiler.End();
            return result;
        }

        public static int FastSerialize<T>(ref T value, byte* buffer, bool pack = true)
        {
            if (null == LSerializeContext.Default) LSerializeContext.InitDefault();

            var context = LSerializeContext.Default;
            LStreamWriter* writer = LStreamWriter.Default;

            context->Clear();
            writer->Clear();

            int size;
            writer->Seek(0, SeekOrigin.Begin);
            Serialize(LSerializeContext.Default, writer, ref value);
            if (pack)
            {
                LStreamBuffer* packed = LStreamBuffer.Create();
                Pack(writer->Buffer, packed);
                size = packed->GetBuffer(buffer);
                packed->Dispose();
                Native.Current.Free(packed);
            }
            else
            {
                size = writer->GetBuffer(buffer);
            }

            LSerializeExtension.Cache.RemoveAllReference();
            return size;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void SerializeUniversal<T>(LSerializeContext* context, LStreamWriter* writer, ref T value)
        {
            writer->WriteUniversal<T>(context, ref value);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void Serialize<T>(LSerializeContext* context, LStreamWriter* writer, ref T value)
        {
            context->ResetRefHeader();
            LFormatterProvider.GetFormatter<T>().Serialize(context, writer, ref value);
            writer->Assemble();
        }

        public static T SafeDeserialize<T>(byte[] buffer, bool unpack = true)
        {
            var context = new LSerializeContext(0);
            T value = default;
            fixed (byte* ptr = &buffer[0])
            {
                LStreamReader reader;
                if (unpack)
                {
                    LStreamBuffer* packed = LStreamBuffer.Create(ptr, buffer.Length);
                    LStreamBuffer* unpacked = LStreamBuffer.Create();
                    Unpack(packed, unpacked);
                    reader = new LStreamReader(unpacked);
                    packed->Dispose();
                    Native.Current.Free(packed);
                }
                else
                {
                    reader = new LStreamReader(ptr, buffer.Length);
                }

                try
                {
                    reader.Seek(0, SeekOrigin.Begin);
                    Deserialize(&context, &reader, ref value);
                    LSerializeExtension.CacheObj.RemoveAllSerializeObj();
                }
                finally
                {
                    reader.Dispose();
                    context.Dispose();
                }
            }

            return value;
        }

        public static T Deserialize<T>(byte[] buffer, bool unpack = true)
        {
            T value = default;
            fixed (byte* ptr = &buffer[0])
            {
                FastDeserialize(ptr, buffer.Length, ref value, unpack);
            }

            return value;
        }

        public static T FastDeserialize<T>(byte* buffer, int length, ref T value, bool unpack = true)
        {
            if (null == LSerializeContext.Default) LSerializeContext.InitDefault();
            var context = LSerializeContext.Default;
            context->Clear();

            var reader = LStreamReader.Default;
            reader->Buffer->EnsureSize(length);
            
            // Log.Info($"fast deserialize buffer size: {length}");

            int size = length;
            if (unpack)
            {
                LStreamBuffer* packed = LStreamBuffer.Create(buffer, length);
                // Log.Info($"Deserialize unpack start: {unpack}, {length}, {BitConverter.ToString(packed->GetBuffer())}");
                size = Unpack(packed, reader->Buffer);
                packed->Dispose();
                Native.Current.Free(packed);
            }
            else
            {
                Native.Utils.Copy(reader->Buffer->Head, buffer, size);
            }
            
            // Log.Info($"fast deserialize unpacked size: {size}");

            reader->Reset(size);
            
            // Log.Info($"Deserialize unpack complete: {unpack}, {reader->Buffer->Size}, {BitConverter.ToString(reader->GetBuffer())}");
            
            Deserialize(context, reader, ref value);
            
            // Log.Info($"fast deserialize success: {value}");
            
            LSerializeExtension.CacheObj.RemoveAllSerializeObj();
            
            // Log.Info($"fast deserialize done");

            return value;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void DeserializeUniversal<T>(LSerializeContext* context, LStreamReader* reader, ref T value)
        {
            reader->ReadUniversal<T>(context, ref value);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void Deserialize<T>(LSerializeContext* context, LStreamReader* reader, ref T value)
        {
            reader->Disassemble();
            context->ResetRefHeader();
            // Log.Info($"fast deserialize formatter size: {reader->Buffer->Size}");
            LFormatterProvider.GetFormatter<T>().Deserialize(context, reader, ref value);
        }
    }
}